package br.poli.ecomp.sma.massim.arch;

import jason.environment.grid.Location;
import jason.util.asl2xml;

import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import br.poli.ecomp.sma.massim.action.Direction;
import br.poli.ecomp.sma.massim.env.WorldModel;

/** thread that writes status data in files (agents' location, mind, ...) */
public class WriteStatusThread extends Thread {

    private static final int MAX_CYCLES_AT_SAME_POS = 11;

	protected Logger logger = Logger.getLogger(WriteStatusThread.class.getName());

    // singleton
    static  WriteStatusThread instance = null;
    
    public static WriteStatusThread create(EcompArch owner, boolean writeLocations, boolean dumpAgsMind) { 
        if (instance == null) {
            instance = new WriteStatusThread();
            instance.owner = owner;
            instance.writeLocations = writeLocations;
            instance.dumpAgsMinds = dumpAgsMind;
            instance.start();
        }
        return instance;
    }

    public static boolean isCreated() {
        return instance != null;
    }

    private WriteStatusThread() {}
    
    private EcompArch owner = null; 
    private boolean    writeLocations = true;
    private boolean    dumpAgsMinds   = true;
    
    private Map<Integer,List<Location>> locations;

    private asl2xml transformer = new asl2xml();

    private static EcompArch[] agents = new EcompArch[WorldModel.agsByTeam];
    
    public static void registerAgent(String name, EcompArch arch) {
        agents[arch.getAgId()] = arch;
    }
    
    public void reset() {
        // init locations
        locations = new HashMap<Integer, List<Location>>();
		for (int i = 0; i < WorldModel.agsByTeam; i++) {
			locations.put(i, new LinkedList<Location>());
		}
    }
    
    public void run() {
        reset();
        
        String fileName = "log/world-status.txt";
        
        PrintWriter out = null;
        try {
            out = new PrintWriter(fileName);

            while (true) {
                try {
                    // write location of the agents
                    if (writeLocations) 
                        writeLocations(out);
                    
                    // store the agents' mind
                    if (dumpAgsMinds)
                        writeMind();
                    
                    // write map
                    /*
                    map.println("\n\n** Agent "+owner.getAgName()+" in cycle "+owner.getSimStep()+"\n");
                    //for (int i=0; i<model.getNbOfAgs(); i++) {
                        // out.println("miner"+(i+1)+" is carrying "+model.getGoldsWithAg(i)+" gold(s), at "+model.getAgPos(i));
                    //}
                    if (owner.getModel() != null) {
                        map.println(owner.getModel().toString());                        
                    }
                    map.flush();
                    */
                    
                } catch (InterruptedException e) { // no problem, quit the thread
                    return;
                } catch (Exception e) {
                    System.out.println("error in writing status "+e);
                    e.printStackTrace();
                    sleep(1000);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
        	if (out != null)
        		out.close();
        }
    }

    private void writeMind() {
        for (EcompArch arch : agents) {
            if (arch == null) break;
            try {
                File dirmind = new File("log/mind/"+arch.getAgName());
                if (!dirmind.exists())
                    dirmind.mkdirs();
                String agmind = transformer.transform(arch.getTS().getAg().getAgState());
                String filename = String.format("%5d.xml",arch.getSimStep()).replaceAll(" ","0");
                FileWriter outmind = new FileWriter(new File(dirmind+"/"+filename));
                outmind.write(agmind);
                outmind.close();
            } catch (Exception e) {
                System.out.println("error getting agent status "+e);                            
            }
        }
    }
    
    private void writeLocations(PrintWriter out) throws InterruptedException {
        long timebefore = System.currentTimeMillis();
        waitNewCycle();
        long cycletime = System.currentTimeMillis() - timebefore;
        
        StringBuilder s = new StringBuilder("Step ").append(owner.getSimStep());
        s.append(String.format("  =  %6d ms", cycletime));

        for (int agId = 0; agId < WorldModel.agsByTeam; agId++) {
            if (agents[agId] != null) {
                Location agp = agents[agId].getLastLocation();
                if (agp != null) {
                	s.append("\n");
                	s.append(String.format("  %d@(%3d,%2d)", agId, agp.x, agp.y));

                	// count how long the agent is in the same location
                	s.append(" / ").append(countCyclesAt(agId, agp)).append(" -> ");
                	s.append(shortActionFormat(agents[agId].getLastAction()));
                }
            }
        }
        
        if (logger.isLoggable(Level.FINEST))
        	logger.info(s.toString());
        out.println(s.toString());
        out.flush();
    }

	private String countCyclesAt(int agId, Location agp) {
		int c = 0;
		Iterator<Location> il = locations.get(agId).iterator();
		while (il.hasNext() && il.next().equals(agp) && c <= MAX_CYCLES_AT_SAME_POS) {
		    c++;
		}
		
		locations.get(agId).add(0,agp);
		
		return (c < 10) ? "*" : ""+c;
	}
    
    public String shortActionFormat(String act) {
        if (act == null) return "? ";
        if (act.equalsIgnoreCase(Direction.EAST.getValue()))        return "e ";
        if (act.equalsIgnoreCase(Direction.NORTHEAST.toString()))   return "ne";
        if (act.equalsIgnoreCase(Direction.SOUTHEAST.toString()))   return "se";
        if (act.equalsIgnoreCase(Direction.WEST.toString()))        return "w ";
        if (act.equalsIgnoreCase(Direction.NORTHWEST.toString()))   return "nw";
        if (act.equalsIgnoreCase(Direction.SOUTHWEST.toString()))   return "sw";
        if (act.equalsIgnoreCase(Direction.NORTH.toString()))       return "n ";
        if (act.equalsIgnoreCase(Direction.SOUTH.toString()))       return "s ";
        if (act.equalsIgnoreCase("skip"))        					return "sk";
        return "!!";
    }
    
    synchronized private void waitNewCycle() throws InterruptedException {
        wait();
    }
    
    synchronized void newCycle() {
        notify();
    }

}
